// --------------------------------------------------------------------------------------------------------------------
// <copyright file="CommandDispatcher.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// <summary>
//   Defines the CommandDispatcher type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.Windows.Input.Commands
{
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;

    public class CommandDispatcher<T> where T : UIElement
    {
        #region Fields

        private ICommand _command;
        private object _commandParameter;
        private WeakReference _target;
        private readonly EventHandler _canExecuteChangedHandler;

        #endregion

        public CommandDispatcher()
        {
            /* =============================================================================
             * We must host a strong reference to the handler as the command architecture 
             * uses a weak event strategy for CanExecuteChanged events.
             ============================================================================ */
            this._canExecuteChangedHandler = CommandCanExecuteChanged;
        }

        public ICommand Command
        {
            get
            {
                return this._command;
            }
            set
            {
                if (this._command != null)
                {
                    this._command.CanExecuteChanged -= this._canExecuteChangedHandler;
                }

                this._command = value;
                if (this._command != null)
                {
                    this._command.CanExecuteChanged += this._canExecuteChangedHandler;
                    OnStateChanged();
                }
            }
        }

        public object CommandParameter
        {
            get
            {
                return this._commandParameter;
            }
            set
            {
                if (this._commandParameter != value)
                {
                    this._commandParameter = value;
                    OnStateChanged();
                }
            }
        }

        public event EventHandler BeforeInvoke;

        public T Target
        {
            get
            {
                return this._target != null ? this._target.Target as T : null;
            }
            set
            {
                var oldTarget = this.Target;
                if (oldTarget != value)
                {
                    this._target = null;
                    if (value != null)
                    {
                        this._target = new WeakReference(value);
                    }
                    OnTargetChanged(oldTarget, value);
                }
            }
        }

        protected virtual void OnInvoke(object sender, EventArgs args)
        {
            if (this.BeforeInvoke != null)
            {
                this.BeforeInvoke(this, EventArgs.Empty);
            }
            ExecuteCommand();
        }

        protected virtual void OnTargetChanged(T oldValue, T newValue)
        {
        }

        protected virtual void OnStateChanged()
        {
            // prevent collection of target
            var target = this.Target;
            if (target != null)
            {
                var control = target as Control;
                if (control != null)
                    control.IsEnabled = CanExecuteCommand(target);
            }
            else
            {
                this.Command = null;
                this.CommandParameter = null;
            }

            // keep alive until this point
            GC.KeepAlive(target);
        }

        protected virtual bool CanExecuteCommand(object target)
        {
            if (this._command == null)
            {
                return false;
            }

            // if bound to a routed command then invoke CanExecute with target as the context
            var routedCommand = this._command as RoutedCommand;
            if (routedCommand != null)
            {
                return routedCommand.CanExecute(target, this._commandParameter);
            }

            // if not a routed command call default CanExecute (No Context)
            return this._command.CanExecute(this._commandParameter);
        }

        protected virtual void ExecuteCommand()
        {
            // get strong reference to target
            var target = this.Target;

            // verify command and target
            if (this._command == null || target == null)
            {
                return;
            }

            // if bound to a routed commend then invoke Execute with target as the context, otherwise call default ICommand.Execute
            var routedCommand = this._command as RoutedCommand;
            if (routedCommand != null)
            {
                routedCommand.Execute(target, this.CommandParameter);
            }
            else
            {
                this._command.Execute(this.CommandParameter);
            }

            // keep alive until this point
            GC.KeepAlive(target);
        }

        private void CommandCanExecuteChanged(object sender, EventArgs e)
        {
            OnStateChanged();
        }
    }
}